﻿using Microsoft.CSharp;
using System;
#if NET45 || NET40
using System.CodeDom.Compiler;
#endif
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web;

namespace HCenter.CommonUtils.Helpers
{
    public static class CodeCompilerHelper
    {
        private static volatile Dictionary<string, Assembly> cache = new Dictionary<string, Assembly>();
        private static object syncRoot = new object();
        static Dictionary<string, Assembly> assemblies = new Dictionary<string, Assembly>();
        public static List<string> OtherAssemblys = new List<string>();
        static CodeCompilerHelper()
        {
            AppDomain.CurrentDomain.AssemblyLoad += (sender, e) =>
            {
                assemblies[e.LoadedAssembly.FullName] = e.LoadedAssembly;
            };
            AppDomain.CurrentDomain.AssemblyResolve += (sender, e) =>
            {
                Assembly assembly = null;
                assemblies.TryGetValue(e.Name, out assembly);
                return assembly;
            };
        }


        public static Assembly CompileCode(string code, string className, string outputAssemblyPath = "")
        {
            //if (string.IsNullOrWhiteSpace(outputAssemblyPath))
            //    outputAssemblyPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "temp\\");

//#if NETSTANDARD2_0
            var compiler = System.CodeDom.Compiler.CodeDomProvider.CreateProvider("CSharp"); //new Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider();// provider.CreateCompiler();
            var compilerparams = new System.CodeDom.Compiler.CompilerParameters();
            compilerparams.GenerateExecutable = false;
            if (!string.IsNullOrWhiteSpace(outputAssemblyPath))
            {
                compilerparams.GenerateInMemory = false;
                if (!System.IO.Directory.Exists(outputAssemblyPath))
                    System.IO.Directory.CreateDirectory(outputAssemblyPath);

                compilerparams.OutputAssembly = System.IO.Path.Combine(outputAssemblyPath, className + ".dll");
            }
            else
            {
                compilerparams.GenerateInMemory = true;
            }
            foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                try
                {
                    string location = assembly.Location;
                    if (!String.IsNullOrEmpty(location))
                    {
                        compilerparams.ReferencedAssemblies.Add(location);
                    }
                }
                catch (NotSupportedException)
                {
                    // this happens for dynamic assemblies, so just ignore it. 
                }
            }
            if (OtherAssemblys?.Count > 0)
            {
                OtherAssemblys.ForEach(f =>
                {
                    if (!string.IsNullOrEmpty(f))
                        compilerparams.ReferencedAssemblies.Add(f);
                });
            }
            var results = compiler.CompileAssemblyFromSource(compilerparams, code);
            if (results.Errors.HasErrors)
            {
                StringBuilder errors = new StringBuilder("Compiler Errors :\r\n");
                foreach (System.CodeDom.Compiler.CompilerError error in results.Errors)
                {
                    errors.AppendFormat("Line {0},{1}\t: {2}\n",
                           error.Line, error.Column, error.ErrorText);
                }
                throw new Exception(errors.ToString());
            }
            else
            {
                AppDomain.CurrentDomain.Load(results.CompiledAssembly.GetName());
                return results.CompiledAssembly;
            }
//#else
//            var compiler = new CSharpCodeProvider();// provider.CreateCompiler();
//            var compilerparams = new CompilerParameters();
//            compilerparams.GenerateExecutable = false;
//            if (!string.IsNullOrWhiteSpace(outputAssemblyPath))
//            {
//                compilerparams.GenerateInMemory = false;
//                if (!System.IO.Directory.Exists(outputAssemblyPath))
//                    System.IO.Directory.CreateDirectory(outputAssemblyPath);

//                compilerparams.OutputAssembly = System.IO.Path.Combine(outputAssemblyPath, className + ".dll");
//            }
//            else
//            {
//                compilerparams.GenerateInMemory = true;
//            }
//            foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
//            {
//                try
//                {
//                    string location = assembly.Location;
//                    if (!String.IsNullOrEmpty(location))
//                    {
//                        compilerparams.ReferencedAssemblies.Add(location);
//                    }
//                }
//                catch (NotSupportedException)
//                {
//                    // this happens for dynamic assemblies, so just ignore it. 
//                }
//            }
//            if (OtherAssemblys?.Count > 0)
//            {
//                OtherAssemblys.ForEach(f =>
//                {
//                    if (!string.IsNullOrEmpty(f))
//                        compilerparams.ReferencedAssemblies.Add(f);
//                });
//            }
//            var results = compiler.CompileAssemblyFromSource(compilerparams, code);
//            if (results.Errors.HasErrors)
//            {
//                StringBuilder errors = new StringBuilder("Compiler Errors :\r\n");
//                foreach (CompilerError error in results.Errors)
//                {
//                    errors.AppendFormat("Line {0},{1}\t: {2}\n",
//                           error.Line, error.Column, error.ErrorText);
//                }
//                throw new Exception(errors.ToString());
//            }
//            else
//            {

//                AppDomain.CurrentDomain.Load(results.CompiledAssembly.GetName());
//                return results.CompiledAssembly;
//            }
//#endif
        }

        public static Assembly CompileCodeOrGetFromCache(string code, string key)
        {
            bool exists = cache.ContainsKey(key);
            if (!exists)
            {
                lock (syncRoot)
                {
                    exists = cache.ContainsKey(key);

                    if (!exists)
                    {
                        cache.Add(key, CompileCode(code, key));
                    }
                }
            }
            return cache[key];
        }
        public static Assembly CompileCodeOrGetFromCache(string key)
        {
            bool exists = cache.ContainsKey(key);
            if (exists)
            {
                return cache[key];
            }
            return null;
        }

    }
}